package graph.editor;

import graph.util.Shapes;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RectangularShape;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.event.MouseInputListener;

import automaton.Transition;

@SuppressWarnings("serial")
public class GraphComponent extends JComponent implements MouseInputListener, KeyListener, Observer {

	/**
	 * 
	 */
	private static int n = 0;
	private static final double EDGE_EPSILON = 2.0;

	protected List<Vertex> vertices = new ArrayList<Vertex>();
	Vertex currentVertex = null;

	private RectangularShape vertexSample = new Ellipse2D.Double(0, 0, 40, 40);

	private RectangularShape currentJointPoint = null;

	protected List<Edge> edges = new ArrayList<Edge>();
	Edge currentEdge = null;

	public GraphComponent() {
		addMouseListener(this);
		addMouseMotionListener(this);
		addKeyListener(this);
	}

	protected void paintComponent(Graphics g) {
		if (isOpaque()) {
			g.setColor(getBackground());
			g.fillRect(0, 0, getWidth(), getHeight());
		}
		g.setColor(getForeground());
		Graphics2D g2 = (Graphics2D) g;
		g2.setStroke(new BasicStroke(GraphEditor.BASIC_STROKE));
		for (Vertex v : vertices)
			v.draw(g2);
		for (Edge e : edges)
			e.draw(g2);
	}

	private Vertex getVertex(int x, int y) {
		for (Vertex v : vertices) {
			if (v.shape.contains(x, y))
				return v;
		}
		return null;
	}

	public void setVertices(List<Vertex> vertices) {
		this.vertices = vertices;
	}

	public void setEdges(List<Edge> edges) {
		this.edges = edges;
	}

	public static void setN(int n) {
		GraphComponent.n = n;
	}

	private RectangularShape getJointPoint(int x, int y) {
		for (Edge e : edges) {
			RectangularShape jp = e.getJointPoint(x, y);
			if (jp != null)
				return jp;
		}
		return null;
	}

	private Edge getEdge(int x, int y) {
		for (Edge e : edges)
			if (e.contains(x, y, EDGE_EPSILON))
				return e;
		return null;
	}

	public List<Vertex> getVertices() {
		return vertices;
	}

	public List<Edge> getEdges() {
		return edges;
	}

	public void setVertexSample(RectangularShape sample) {
		vertexSample = sample;
	}

	private RectangularShape newVertexShape(int x, int y) {
		RectangularShape rs = (RectangularShape) vertexSample.clone();
		Shapes.moveShape(rs, x, y);
		return rs;
	}

	private Vertex createVertex(int x, int y) {
		RectangularShape rs = newVertexShape(x, y);
		Vertex v = new Vertex(rs, Integer.toString(n++));
		vertices.add(v);
		return v;
	}

	private void removeVertex(Vertex v) {
		List<Edge> toRemove = new ArrayList<Edge>();
		for (Edge e : edges) {
			if (e.v1 == v || e.v2 == v)
				toRemove.add(e);
		}
		for (Edge e : toRemove)
			removeEdge(e);
		vertices.remove(v);
	}

	private void removeEdge(Edge e) {
		remove(e.textField);
		edges.remove(e);
	}

	private Edge startEdge(Vertex v) {
		RectangularShape rs2 = newVertexShape(0, 0);
		RectangularShape rs = v.shape;
		rs2.setFrameFromCenter((int) rs.getCenterX(), (int) rs.getCenterY(), (int) rs.getCenterX(),
				(int) rs.getCenterY());
		Edge l = new Edge(v, new Vertex(rs2, null));
		edges.add(l);
		return l;
	}

	private void addJointPoint(Edge e) {
		e.addJointPoint();
	}

	private void endLine(Edge e, int x, int y) {
		Vertex v = getVertex(x, y);
		if (v == null) {
			e.v2.shape.setFrameFromCenter(x, y, x + vertexSample.getHeight() / 2, y + vertexSample.getWidth()
					/ 2);
			e.v2.label = Integer.toString(n++);
			vertices.add(e.v2);
		} else
			e.v2 = v;
	}

	public void mousePressed(MouseEvent e) {
		requestFocusInWindow();
		if ((e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
			return;
		int x = e.getX();
		int y = e.getY();
		Vertex v = getVertex(x, y);
		if (v == null) {
			currentJointPoint = getJointPoint(x, y);
		}
		if (v == null && currentJointPoint == null)
			v = createVertex(x, y);
		if (v != null && e.isAltDown())
			currentEdge = startEdge(v);
		else
			currentVertex = v;
		if (v != null && e.isControlDown()) {
			v.getState().setInitial(true);
			v.setColor(Color.RED);
			repaint();
			JOptionPane.showMessageDialog(null, v.label + " set to Initial state");
		}
		if (v != null && e.isShiftDown()) {
			v.getState().setTerminal(true);
			v.setColor(Color.GREEN);
			repaint();
			JOptionPane.showMessageDialog(null, v.label + " set to Final state");
		}
		repaint();
	}

	public void mouseDragged(MouseEvent e) {
		if (currentVertex != null) {
			Shapes.moveShape(currentVertex.shape, e.getX(), e.getY());
			repaint();
		} else if (currentEdge != null) {
			Shapes.moveShape(currentEdge.v2.shape, e.getX(), e.getY());
			repaint();
		} else if (currentJointPoint != null) {
			Shapes.moveShape(currentJointPoint, e.getX(), e.getY());
			repaint();
		}
	}

	public void addEdgeLabel(final Edge e) {
		final JTextField textField = new JTextField();
		Font font = new Font(textField.getFont().getName(), Font.BOLD, 16);
		textField.setForeground(Color.BLUE);
		textField.setFont(font);
		textField.setText("none");
		textField.setBorder(BorderFactory.createEmptyBorder());
		textField.setOpaque(false);
		textField.setSize(50, 40);
		textField.setLocation(e.labelPosition());
		e.textField = textField;
		this.add(textField);
	}

	public void mouseReleased(MouseEvent e) {
		if (currentEdge != null) {
			endLine(currentEdge, e.getX(), e.getY());
			addEdgeLabel(currentEdge);
			currentEdge = null;
			repaint();
		}
		currentVertex = null;
		currentJointPoint = null;
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mouseClicked(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON3) {
			int x = e.getX();
			int y = e.getY();
			Vertex v = getVertex(x, y);
			if (v != null) {
				removeVertex(v);
				repaint();
				return;
			}
			for (Edge edge : edges) {
				RectangularShape jp = edge.getJointPoint(x, y);
				if (jp != null) {
					edge.removeJointPoint(jp);
					repaint();
					return;
				}
			}
			Edge edge = getEdge(x, y);
			if (edge != null) {
				removeEdge(edge);
				repaint();
				return;
			}
		}
		// if (e.getClickCount() == 2) {
		// int x = e.getX();
		// int y = e.getY();
		// Vertex v = getVertex(x, y);
		// v.setColor(Color.ORANGE);
		// repaint();
		// }
	}

	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == ' ' && currentEdge != null) {
			addJointPoint(currentEdge);
		}
	}

	public void keyReleased(KeyEvent e) {
	}

	public void keyTyped(KeyEvent e) {
	}

	public static void saveFile(String destPath, GraphComponent components) throws IOException {
		FileOutputStream fileOut = new FileOutputStream(destPath);
		ObjectOutputStream objOutStream = new ObjectOutputStream(fileOut);
		ArrayList<List<?>> tempArray = new ArrayList<List<?>>();
		tempArray.add(components.getEdges());
		tempArray.add(components.getVertices());
		objOutStream.writeObject(tempArray);
		objOutStream.flush();
		objOutStream.close();
	}

	@SuppressWarnings("unchecked")
	public static void loadFile(String sourcePath, GraphComponent components) throws IOException,
			ClassNotFoundException {
		components.removeAll();
		FileInputStream fileIn = new FileInputStream(sourcePath);
		ObjectInputStream objInStream = new ObjectInputStream(fileIn);
		ArrayList<List<?>> tempArray = (ArrayList<List<?>>) objInStream.readObject();
		ArrayList<Vertex> listVertex = (ArrayList<Vertex>) tempArray.get(1);
		components.setEdges((List<Edge>) tempArray.get(0));
		components.setVertices(listVertex);
		for (Object edge : tempArray.get(0)) {
			Edge edgeMain = (Edge) edge;
			components.add(edgeMain.getTextField());
		}
		int n = 0;
		for (Vertex vertex : listVertex) {
			if (Integer.parseInt(vertex.getLabel()) > n) {
				GraphComponent.setN(Integer.parseInt(vertex.getLabel()) + 1);
			}
		}
		objInStream.close();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void update(Observable o, Object arg) {
		Transition<String> cur = (Transition<String>) arg;
		Edge e = transformTransToEdge(cur);
		if (e != null) {
			// JOptionPane.showMessageDialog(null, e.getSource().getLabel() +
			// "----" + e.getLable() + "--->"
			// + e.getDestination().getLabel());
			e.getSource().setColor(Color.GRAY);
			repaint();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			e.getDestination().setColor(Color.RED);
			repaint();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
		}
	}

	public void resetAutomaton() {

	}

	public Edge transformTransToEdge(Transition<String> transition) {
		for (Edge e : edges) {
			if (transition.source().equals(e.getSource().getState())
					&& (transition.label().equals(e.getLable()))
					&& (transition.target().equals(e.getDestination().getState())))
				return e;
		}
		return null;
	}
}
